/// control and status register
pub const csr = enum(u12) {
    /// read-only shadow of the memory-mapped mtime register
    time = 0xC01,
    /// Upper 32-bits of time, RV32 only
    timeh = 0xC81,

    /// Supervisor Status
    sstatus = 0x100,
    /// Supervisor Interrupt Enable
    sie = 0x104,
    /// Supervisor Trap Vector Base Address
    stvec = 0x105,
    /// Supervisor Couunter Enable
    scounteren = 0x106,

    /// Supervisor Environment Configuration
    senvcfg = 0x10A,

    /// Supervisor Scratch
    sscratch = 0x140,
    /// Supervisor Exception Program Counter
    sepc = 0x141,
    /// Supervisor Cause
    scause = 0x142,
    /// Supervisor Trap Value
    stval = 0x143,
    /// Supervisor Interrupt Pending
    sip = 0x144,

    /// Supervisor Address Translation and Protection
    satp = 0x180,

    /// Supervisor Timer
    stimecmp = 0x14D,
    /// Upper 32-bits of stimecmp, RV32 only
    stimecmph = 0x15D,

    /// Hart ID
    mhartid = 0xF14,

    /// Machine Status
    mstatus = 0x300,
    /// Machine Exception Delegation
    medeleg = 0x302,
    /// Machine Interrupt Delegation
    mideleg = 0x303,
    /// Machine Interrupt Enable
    mie = 0x304,
    /// Machine Trap Vector Base Address
    mtvec = 0x305,
    /// Machine Counter Enable
    mcounteren = 0x306,
    /// Additionnal machine status register, RV32 only
    mstatush = 0x310,

    /// Machine Scratch
    mscratch = 0x340,
    /// Machine Exception Program Counter
    mepc = 0x341,
    /// Machine Cause
    mcause = 0x342,
    /// Machine Trap Value
    mtval = 0x343,
    /// Machine Interrupt Pending
    mip = 0x344,

    /// Machine Environment Configuration
    menvcfg = 0x30A,
    /// Upper 32-bits of menvcfg, RV32 only
    menvcfgh = 0x31A,

    /// PMP configuration registers
    pmpcfg0 = 0x3A0,
    /// PMP address registers
    pmpaddr0 = 0x3B0,

    /// Machine Timer
    mtime,
    /// Machine Timer Compare
    mtimecmp,

    /// Returns the type-safe wrapper for a CSR register.
    /// For typed registers, returns their declared type.
    /// Falls back to usize for standard registers.
    pub fn Type(comptime tag: csr) type {
        const name = @tagName(tag);
        return if (@hasDecl(top, name)) @field(top, name) else usize;
    }

    /// Returns the unsigned integer representation of a CSR's storage type.
    /// For typed registers, returns an integer matching their bit width.
    /// Falls back to usize for standard registers.
    pub fn Int(comptime tag: csr) type {
        return std.meta.Int(.unsigned, @bitSizeOf(tag.Type()));
    }

    /// Reads the current value of the specified CSR.
    /// Returns the value as the register's corresponding type.
    pub fn read(comptime tag: csr) Type(tag) {
        if (tag.isSplit()) {
            var vs: [2]u32 = undefined;
            vs[0] = raw.read(tag);
            vs[1] = raw.read(tag.high());
            return tag.typeFromInt(@bitCast(vs));
        } else {
            return tag.typeFromInt(raw.read(tag));
        }
    }

    /// Writes a new value to the specified CSR.
    /// Note: This overwrites the entire register contents.
    pub fn write(comptime tag: csr, register: Type(tag)) void {
        tag.modify(register, raw.write);
    }

    /// Sets (ORs) bits in the CSR specified by the mask.
    /// Only bits set to 1 in the mask will be set in the CSR.
    pub fn set(comptime tag: csr, register: Type(tag)) void {
        tag.modify(register, raw.set);
    }

    /// Clears bits in the CSR specified by the mask.
    /// Only bits set to 1 in the mask will be cleared in the CSR.
    pub fn clear(comptime tag: csr, register: Type(tag)) void {
        tag.modify(register, raw.clear);
    }

    /// Atomically swaps the value of the specified CSR.
    /// Note: This overwrites the entire register contents.
    pub fn swap(comptime tag: csr, register: Type(tag)) Type(tag) {
        return tag.typeFromInt(raw.swap(tag, tag.intFromType(register)));
    }

    /// Atomically sets bits in the CSR and returns the original value.
    /// Only bits set to 1 in the mask will be set (ORed) in the CSR.
    pub fn readSet(comptime tag: csr, mask: Type(tag)) Type(tag) {
        return tag.typeFromInt(raw.readSet(tag, tag.intFromType(mask)));
    }

    /// Atomically clears bits in the CSR and returns the original value.
    /// Only bits set to 1 in the mask will be cleared in the CSR.
    pub fn readClear(comptime tag: csr, mask: Type(tag)) Type(tag) {
        return tag.typeFromInt(raw.readClear(tag, tag.intFromType(mask)));
    }

    /// Applies a CSR operation function, handling 64-bit registers in RV32 by
    /// splitting them into high/low 32-bit parts.
    inline fn modify(
        comptime tag: csr,
        register: Type(tag),
        func: fn (comptime csr, usize) callconv(.@"inline") void,
    ) void {
        if (tag.isSplit()) {
            const vs: [2]u32 = @bitCast(tag.intFromType(register));
            func(tag, vs[0]);
            func(tag.high(), vs[1]);
        } else {
            func(tag, tag.intFromType(register));
        }
    }

    /// Checks if a CSR has a high-byte counterpart register in RV32 architecture.
    inline fn isSplit(comptime tag: csr) bool {
        return is_rv32 and @hasField(csr, @tagName(tag) ++ "h");
    }

    /// Returns the high-byte counterpart CSR for a given CSR tag
    inline fn high(comptime tag: csr) csr {
        return @field(csr, @tagName(tag) ++ "h");
    }

    fn typeFromInt(comptime tag: csr, value: Int(tag)) Type(tag) {
        return switch (@typeInfo(Type(tag))) {
            .int => value,
            .@"enum" => @enumFromInt(value),
            .@"struct" => @bitCast(value),
            .pointer => @ptrFromInt(value),
            else => |T| @compileError("unsupported type" ++ T),
        };
    }

    fn intFromType(comptime tag: csr, value: anytype) Int(tag) {
        return switch (@typeInfo(@TypeOf(value))) {
            .int => value,
            .@"enum" => @intFromEnum(value),
            .@"struct" => @bitCast(value),
            .pointer => @intFromPtr(value),
            else => |T| @compileError("unsupported type" ++ T),
        };
    }
};

/// Direct CSR operations using raw integer values.
/// These bypass type safety and work directly with the underlying machine words.
const raw = struct {
    inline fn read(comptime tag: csr) usize {
        const name = @tagName(tag);
        return asm volatile ("csrr %[ret], " ++ name
            : [ret] "=r" (-> usize),
        );
    }

    inline fn write(comptime tag: csr, value: usize) void {
        const name = @tagName(tag);
        asm volatile ("csrw " ++ name ++ ", %[value]"
            :
            : [value] "r" (value),
        );
    }

    inline fn set(comptime tag: csr, mask: usize) void {
        const name = @tagName(tag);
        asm volatile ("csrs " ++ name ++ ", %[mask]"
            :
            : [mask] "r" (mask),
        );
    }

    inline fn clear(comptime tag: csr, mask: usize) void {
        const name = @tagName(tag);
        asm volatile ("csrc " ++ name ++ ", %[mask]"
            :
            : [mask] "r" (mask),
        );
    }

    inline fn swap(comptime tag: csr, value: usize) usize {
        const name = @tagName(tag);
        return asm volatile ("csrrw %[ret], " ++ name ++ ", %[value]"
            : [ret] "=r" (-> usize),
            : [value] "r" (value),
        );
    }

    inline fn readSet(comptime tag: csr, mask: usize) usize {
        const name = @tagName(tag);
        return asm volatile ("csrrs %[ret], " ++ name ++ ", %[mask]"
            : [ret] "=r" (-> usize),
            : [mask] "r" (mask),
        );
    }

    inline fn readClear(comptime tag: csr, mask: usize) usize {
        const name = @tagName(tag);
        return asm volatile ("csrrc %[ret], " ++ name ++ ", %[mask]"
            : [ret] "=r" (-> usize),
            : [mask] "r" (mask),
        );
    }
};

// Special registers with non-standard types (not usize)
pub const mepc = *allowzero const anyopaque;
pub const mtvec = *align(4) const fn () callconv(.naked) noreturn;
pub const sepc = mepc;
pub const stvec = mtvec;
pub const stimecmp = u64;
pub const time = u64;

/// Machine Status Register
pub const mstatus = packed struct(u64) {
    @"0": u1 = 0,
    sie: bool = false,

    @"2": u1 = 0,
    mie: bool = false,

    @"4": u1 = 0,
    spie: bool = false,
    ube: bool = false,
    mpie: bool = false,
    spp: bool = false,
    vs: u2 = 0,
    mpp: Mpp = .user,

    _: u64Minus(13) = 0,

    pub const Mpp = enum(u2) {
        user = 0b00,
        supervisor = 0b01,
        hypervisor = 0b10,
        machine = 0b11,

        /// Use this to CLEAR the field
        pub const clear_mask: Mpp = .machine;
    };
};

/// Machine cause register
pub const mcause = cause;

/// Supervisor Status Register
pub const sstatus = packed struct(usize) {
    @"0": u1 = 0,
    sie: bool = false,

    @"2-4": u3 = 0,
    spie: bool = false,
    ube: bool = false,

    @"7": u1 = 0,
    spp: bool = false,

    _: XlenMinus(9) = 0,
};

pub const menvcfg = packed struct(u64) {
    fiom: bool = false,
    @"1-3": u3 = 0,

    cbie: u2 = 0,
    cbcfe: bool = false,
    cbze: bool = false,

    @"8-31": u24 = 0,
    pmm: u2 = 0,

    @"34-59": u26 = 0,
    cde: bool = false,
    adue: bool = false,
    pbmte: bool = false,
    stce: bool = false,
};

pub const mcounteren = packed struct(u32) {
    cy: bool = false,
    tm: bool = false,
    ir: bool = false,
    hpm: u29 = 0,
};

/// Machine-mode Interrupt Enable
pub const mie = packed struct(usize) {
    @"0": u1 = 0,
    ssie: bool = false,

    @"2": u1 = 0,
    msie: bool = false,

    @"4": u1 = 0,
    stie: bool = false,

    @"6": u1 = 0,
    mtie: bool = false,

    @"8": u1 = 0,
    seie: bool = false,

    @"10": u1 = 0,
    meie: bool = false,

    _: XlenMinus(12) = 0,
};

/// Supervisor interrupt-pending register
pub const sip = packed struct(usize) {
    usip: bool = false,
    ssip: bool = false,

    @"2-3": u2 = 0,
    utip: bool = false,
    stip: bool = false,

    @"6-7": u2 = 0,
    ueip: bool = false,
    seip: bool = false,

    _: XlenMinus(10) = 0,
};

/// Supervisor Interrupt Enable
pub const sie = packed struct(usize) {
    @"0": u1 = 0,
    ssie: bool = false,

    @"2-4": u3 = 0,
    stie: bool = false,

    @"6-8": u3 = 0,
    seie: bool = false,

    _: XlenMinus(10) = 0,
};

/// Supervisor Address Translation and Protection Register
pub const satp = if (is_rv32) satp32 else satp64;

const satp32 = packed struct(u32) {
    /// Physical Page Number
    ppn: u22 = 0,

    /// Address Space Identifier (optional)
    asid: u9 = 0,

    mode: enum(u1) {
        none = 0,
        sv32 = 1,
    } = .none,
};

const satp64 = packed struct(u64) {
    /// Physical Page Number
    ppn: u44 = 0,

    /// Address Space Identifier (optional)
    asid: u16 = 0,

    mode: enum(u4) {
        none = 0,
        sv39 = 8,
        sv48 = 9,
    } = .none,
};

/// Supervisor cause register
pub const scause = cause;

const cause = packed struct(usize) {
    code: Code,
    interrupt: bool = false,

    pub const Code = packed union {
        interrupt: Interrupt,
        exception: Exception,
    };

    pub const Interrupt = enum(XlenMinus(1)) {
        @"User software interrupt" = 0,
        @"Supervisor software interrupt" = 1,
        @"Machine software interrupt" = 3,

        @"User timer interrupt" = 4,
        @"Supervisor timer interrupt" = 5,
        @"Machine timer interrupt" = 7,

        @"User external interrupt" = 8,
        @"Supervisor external interrupt" = 9,
        @"Machine external interrupt" = 11,

        _,
    };

    pub const Exception = enum(XlenMinus(1)) {
        @"Instruction address misaligned" = 0,
        @"Instruction address fault" = 1,
        @"Illegal instruction" = 2,
        Breakpoint = 3,
        @"Load address misaligned" = 4,
        @"Load access fault" = 5,
        @"Store/AMO address misaligned" = 6,
        @"Store/AMO access fault" = 7,
        @"Environment call from U-mode" = 8,
        @"Environment call from S-mode" = 9,

        @"Environment call from M-mode" = 11,
        @"Instruction page fault" = 12,
        @"Load page fault" = 13,

        @"Store/AMO page fault" = 15,

        _,
    };

    pub fn format(self: cause, writer: anytype) !void {
        // If the non-exhaustive enum value does not map to a name, it invokes safety-checked Undefined Behavior.
        if (self.interrupt) {
            try writer.print("{s}", .{@tagName(self.code.interrupt)});
        } else {
            try writer.print("{s}", .{@tagName(self.code.exception)});
        }
    }
};

fn XlenMinus(n: u16) type {
    return std.meta.Int(.unsigned, @bitSizeOf(usize) - n);
}

fn u64Minus(n: u16) type {
    return std.meta.Int(.unsigned, 64 - n);
}

const std = @import("std");
const top = @This();
const is_rv32 = @bitSizeOf(usize) == 32;
